Blocking vs Non-Blocking Resources - Complete Guide
Table of Contentsโ
- What Does "Blocking" Mean?
- The Critical Rendering Path
- Blocking Resources
- Non-Blocking Resources
- Resource Loading Matrix
- How Browsers Decide Priority
- Optimization Strategies
- Visual Flow Diagrams
- Real Interview Scenarios
- Quick Reference
What Does "Blocking" Mean?โ
A blocking resource is one that prevents the browser from continuing a critical step in the rendering process.
What Can Be Blocked?โ
- HTML parsing - Browser stops reading HTML
- DOM construction - Building the DOM tree pauses
- CSSOM construction - Must wait for CSS to parse
- Page rendering - Cannot paint pixels until ready
Non-Blocking Resourcesโ
Non-blocking resources load in parallel without stopping these critical steps. The browser continues parsing and rendering while these resources download.
Why This Mattersโ
Blocking resources directly impact Core Web Vitals:
- FCP (First Contentful Paint) - When first content appears
- LCP (Largest Contentful Paint) - When main content appears
- TTI (Time to Interactive) - When page becomes interactive
The Critical Rendering Pathโ
The browser follows this sequence to render a page:
1. HTML โ DOM (Document Object Model)
2. CSS โ CSSOM (CSS Object Model)
3. DOM + CSSOM โ Render Tree
4. Render Tree โ Layout (calculate positions)
5. Layout โ Paint (draw pixels)
Key Principleโ
Any resource that delays DOM or CSSOM construction blocks rendering.
Example Flowโ
User requests page
โ
Download HTML (200ms)
โ
Parse HTML โ Build DOM
โ (finds CSS)
Download CSS (300ms) โ BLOCKS rendering
โ
Parse CSS โ Build CSSOM
โ
Combine DOM + CSSOM โ Render Tree
โ
Layout + Paint
โ
First Contentful Paint โ
Blocking Resourcesโ
A. CSS (Render-Blocking)โ
CSS is render-blocking - the browser cannot paint pixels until CSSOM is built.
Example:
<link rel="stylesheet" href="styles.css">
Why Blocking?โ
- Layout depends on CSS - Need to know sizes, positions, colors
- Prevents FOUC - Flash of Unstyled Content
- CSSOM must be complete - Can't render partial styles
Blocking Behaviorโ
| Blocks HTML Parsing? | Blocks Rendering? |
|---|---|
| โ No | โ Yes |
Note: CSS does NOT block HTML parsing - the browser continues building the DOM. But it DOES block rendering.
Example Timelineโ
0ms: Start HTML parse
100ms: Discover <link rel="stylesheet">
100ms: Start CSS download (HTML parsing continues)
300ms: CSS downloaded
350ms: CSSOM built
350ms: Render Tree created โ CSS was blocking this
400ms: First Paint
Media Queries Exceptionโ
CSS with non-matching media queries is NOT render-blocking:
<!-- Blocks rendering on all devices -->
<link rel="stylesheet" href="styles.css">
<!-- Only blocks on print -->
<link rel="stylesheet" href="print.css" media="print">
<!-- Only blocks on mobile -->
<link rel="stylesheet" href="mobile.css" media="(max-width: 640px)">
B. Synchronous JavaScript (Parse-Blocking & Render-Blocking)โ
Synchronous JavaScript blocks both HTML parsing and rendering.
Example:
<script src="app.js"></script>
Why Blocking?โ
- JavaScript can modify DOM -
document.write(), element creation - JavaScript can modify CSSOM - Change styles dynamically
- Execution order matters - Scripts must run in sequence
- CSSOM dependency - Script execution waits for CSS to finish
Blocking Behaviorโ
| Blocks HTML Parsing? | Blocks Rendering? |
|---|---|
| โ Yes | โ Yes |
Example Timelineโ
0ms: Start HTML parse
100ms: Discover <script src="app.js">
100ms: STOP HTML parsing โ BLOCKED
100ms: Download app.js
400ms: Download complete
400ms: Execute JavaScript
500ms: Resume HTML parsing โ UNBLOCKED
JavaScript Waits for CSSโ
Even worse - JavaScript execution waits for pending CSS downloads:
<link rel="stylesheet" href="styles.css">
<script src="app.js"></script>
Timeline:
0ms: Parse HTML
50ms: Discover CSS, start download
100ms: Discover script
100ms: Script download starts (but execution waits)
300ms: CSS finishes (CSSOM ready)
300ms: Script finishes download
300ms: NOW script can execute โ Waited for CSS!
Why? Script might query computed styles, so browser ensures CSSOM is ready first.
C. Fonts (Indirectly Blocking)โ
Fonts don't block parsing or initial rendering, but they delay text rendering, causing FOIT (Flash of Invisible Text).
Example:
@font-face {
font-family: 'Inter';
src: url('Inter.woff2') format('woff2');
}
body {
font-family: Inter, sans-serif;
}
Problem: FOITโ
0ms: Page renders, but text is invisible
0ms: Font downloading...
500ms: Font loaded
500ms: Text suddenly appears โ Bad UX
Problem: FOUTโ
With font-display: swap:
0ms: Page renders with fallback font
0ms: Font downloading...
500ms: Font loaded
500ms: Text layout shifts โ CLS issue
Blocking Behaviorโ
| Blocks HTML Parsing? | Blocks Rendering? | Blocks Text? |
|---|---|---|
| โ No | โ No | โ ๏ธ Yes (3s timeout) |
Mitigation Strategiesโ
1. Preload Critical Fonts
<link rel="preload"
href="/fonts/Inter-Regular.woff2"
as="font"
type="font/woff2"
crossorigin>
2. Use font-display
@font-face {
font-family: 'Inter';
src: url('Inter.woff2') format('woff2');
font-display: swap; /* Show fallback immediately */
}
3. Subset Fonts
Full font: 150 KB
Subset: 30 KB (only needed characters)
Non-Blocking Resourcesโ
A. Imagesโ
Images load asynchronously and do NOT block parsing or rendering.
Example:
<img src="image.jpg" alt="Description">
Non-Blocking Behaviorโ
| Blocks HTML Parsing? | Blocks Rendering? |
|---|---|
| โ No | โ No |
How It Worksโ
0ms: Parse HTML
50ms: Discover <img>
50ms: Continue parsing (doesn't wait)
50ms: Start image download in background
200ms: HTML parsing complete
250ms: First paint (without image)
400ms: Image loads, reflow/repaint
Exception: LCP Imageโ
If the image is the Largest Contentful Paint element, late loading hurts performance.
Problem:
<!-- Hero image discovered late -->
<img src="/hero.jpg" alt="Hero">
Solution:
<!-- Preload + high priority -->
<link rel="preload" href="/hero.jpg" as="image">
<img src="/hero.jpg"
alt="Hero"
fetchpriority="high"
loading="eager">
B. Async JavaScriptโ
Async JavaScript downloads in parallel and executes as soon as ready, without blocking HTML parsing.
Example:
<script src="app.js" async></script>
Async Behaviorโ
| Blocks HTML Parsing? | Blocks Rendering? | Execution Order? |
|---|---|---|
| โ No (during download) | โ No (during download) | โ ๏ธ Not guaranteed |
| โ ๏ธ Yes (during execution) | โ ๏ธ Yes (during execution) | - |
Timelineโ
0ms: Parse HTML
50ms: Discover <script async>
50ms: Start download (parsing continues)
100ms: Still parsing HTML
200ms: Script finishes download
200ms: PAUSE parsing, execute script โ Brief block
250ms: Resume parsing
When to Use Asyncโ
โ Good for:
- Analytics scripts (Google Analytics)
- Ad networks
- Social media widgets
- Any independent script that doesn't depend on DOM
โ Bad for:
- Scripts that manipulate DOM
- Scripts with dependencies
- Scripts that must run in order
Example:
<!-- โ
Good: Independent -->
<script src="https://www.google-analytics.com/analytics.js" async></script>
<!-- โ Bad: Order matters -->
<script src="jquery.js" async></script>
<script src="app.js" async></script> <!-- Might run before jQuery! -->
C. Defer JavaScript (Recommended Default)โ
Defer JavaScript downloads in parallel but waits to execute until HTML parsing is complete.
Example:
<script src="app.js" defer></script>
Defer Behaviorโ
| Blocks HTML Parsing? | Blocks Rendering? | Execution Order? |
|---|---|---|
| โ No | โ No | โ Guaranteed |
Timelineโ
0ms: Parse HTML
50ms: Discover <script defer>
50ms: Start download (parsing continues)
100ms: Script downloaded (keeps parsing)
200ms: HTML parsing complete
200ms: DOMContentLoaded about to fire
200ms: Execute all deferred scripts (in order)
250ms: DOMContentLoaded fires
Why Defer is Bestโ
- Non-blocking - Downloads in parallel
- Predictable - Executes in order
- DOM ready - Full DOM available when script runs
- Performance - Doesn't delay FCP
When to Use Deferโ
โ Use defer for:
- Application JavaScript
- UI frameworks (React, Vue)
- DOM manipulation scripts
- Any script that needs the full DOM
Example:
<!-- All defer, execute in order -->
<script src="utils.js" defer></script>
<script src="components.js" defer></script>
<script src="app.js" defer></script>
D. Lazy-Loaded Resourcesโ
Resources loaded only when needed (typically when scrolled into view).
Example:
<img src="image.jpg" loading="lazy" alt="Below fold">
Lazy Loading Behaviorโ
| Blocks HTML Parsing? | Blocks Rendering? | When Loaded? |
|---|---|---|
| โ No | โ No | When near viewport |
Benefitsโ
- Saves bandwidth - Only loads visible content
- Faster initial load - Fewer requests
- Better performance - Prioritizes above-fold content
Example Usageโ
<!-- Above-the-fold: Load immediately -->
<img src="hero.jpg" loading="eager" fetchpriority="high">
<!-- Below-the-fold: Lazy load -->
<img src="gallery-1.jpg" loading="lazy">
<img src="gallery-2.jpg" loading="lazy">
<img src="gallery-3.jpg" loading="lazy">
Native vs JavaScriptโ
<!-- Native (preferred) -->
<img src="image.jpg" loading="lazy">
<!-- JavaScript (for older browsers) -->
<img data-src="image.jpg" class="lazy">
<script>
// Intersection Observer implementation
</script>
Resource Loading Matrixโ
Quick reference table showing what each resource type blocks.
| Resource | Blocks Parsing? | Blocks Rendering? | Notes |
|---|---|---|---|
| HTML | N/A | N/A | Sequential parsing |
| CSS | โ No | โ Yes | Render-blocking |
| JS (sync) | โ Yes | โ Yes | Parse & render blocking |
| JS (async) | โ No* | โ No* | *Execution can block briefly |
| JS (defer) | โ No | โ No | Executes after parsing |
| Images | โ No | โ No | Always non-blocking |
| Fonts | โ No | โ ๏ธ Indirect | FOIT - text waits for font |
| Videos | โ No | โ No | Always non-blocking |
| Iframes | โ No | โ No | Independent document |
Visual Priorityโ
Highest Priority (Blocking):
โโ HTML parsing
โโ CSS (CSSOM construction)
โโ Synchronous JavaScript
Medium Priority (Non-blocking but important):
โโ Deferred JavaScript
โโ Async JavaScript
โโ Preloaded resources
Low Priority (Opportunistic):
โโ Images (below fold)
โโ Lazy-loaded content
โโ Prefetched resources
How Browsers Decide Priorityโ
Understanding browser prioritization helps optimize loading strategies.
1. HTML is Parsed Sequentiallyโ
<html>
<head>
<!-- Parsed first -->
</head>
<body>
<!-- Parsed second -->
</body>
</html>
Browser discovers resources in document order and assigns priority based on:
- Resource type
- Location in document
- Attributes (async/defer)
- Media queries
2. CSSOM Must Be Complete Before Paintingโ
Cannot paint without CSSOM because:
โโ Layout calculations need dimensions
โโ Visual styles need colors/fonts
โโ Positioning needs CSS rules
This is why CSS is render-blocking - it's not optional for rendering.
3. JavaScript Can Block Because It Mutates DOM/CSSOMโ
JavaScript has the power to:
// Modify DOM
document.write('<div>New content</div>');
document.getElementById('header').remove();
// Modify CSSOM
element.style.color = 'red';
document.body.classList.add('dark-mode');
// Query computed styles (requires CSSOM)
const width = element.offsetWidth;
const color = getComputedStyle(element).color;
Because of this power, browsers must:
- Stop parsing when encountering
<script> - Wait for CSSOM if script might query styles
- Execute script before continuing
4. Images Are Independent from Layout (Mostly)โ
Images don't affect:
- HTML structure (DOM)
- CSS rules (CSSOM)
- JavaScript execution
- Other resource loading
Exception: Image dimensions can cause reflow if not specified.
Good practice:
<!-- Prevents layout shift -->
<img src="photo.jpg" width="800" height="600" alt="Photo">
Browser Priority Levelsโ
Browsers assign priority levels to resources:
| Priority | Resource Types |
|---|---|
| Highest | HTML document |
| High | CSS (render-blocking) |
| High | Fonts (in use) |
| High | Sync scripts (in <head>) |
| High | Preloaded resources |
| Medium | Scripts (defer/async) |
| Medium | Images (above fold) |
| Low | Images (below fold) |
| Lowest | Prefetched resources |
You can override these with fetchpriority:
<img src="hero.jpg" fetchpriority="high">
<script src="analytics.js" fetchpriority="low"></script>
Optimization Strategiesโ
CSS Optimizationsโ
1. Inline Critical CSSโ
<head>
<!-- Inline critical above-the-fold CSS -->
<style>
.hero { display: flex; height: 100vh; }
.nav { position: fixed; top: 0; }
</style>
<!-- Load full CSS asynchronously -->
<link rel="preload" href="/styles.css" as="style"
onload="this.rel='stylesheet'">
<noscript>
<link rel="stylesheet" href="/styles.css">
</noscript>
</head>
Impact: Reduces render-blocking time from 300ms to 0ms.
2. Split CSS by Media Queryโ
<!-- Only blocks on matching media -->
<link rel="stylesheet" href="mobile.css" media="(max-width: 768px)">
<link rel="stylesheet" href="desktop.css" media="(min-width: 769px)">
<link rel="stylesheet" href="print.css" media="print">
3. Minify and Compressโ
# Before
styles.css: 150 KB
# After minification
styles.min.css: 120 KB (-20%)
# After gzip
styles.min.css.gz: 25 KB (-83% total)
JavaScript Optimizationsโ
1. Use Defer by Defaultโ
<!-- โ Bad: Blocks parsing -->
<script src="/app.js"></script>
<!-- โ
Good: Non-blocking -->
<script src="/app.js" defer></script>
2. Code Splittingโ
// Before: One large bundle
app.js: 500 KB
// After: Split by route
main.js: 50 KB (critical)
home.chunk.js: 80 KB (lazy)
about.chunk.js: 60 KB (lazy)
products.chunk.js: 120 KB (lazy)
Implementation (Webpack/Vite):
// Dynamic import
const AboutPage = () => import('./pages/About');
// React.lazy
const About = React.lazy(() => import('./pages/About'));
3. Tree Shakingโ
Remove unused code:
// Before
import _ from 'lodash'; // 70 KB
// After
import debounce from 'lodash/debounce'; // 2 KB
4. Avoid Long Main-Thread Tasksโ
// โ Bad: Long blocking task
function processItems(items) {
items.forEach(item => {
// Complex calculations
heavyOperation(item);
});
} // Blocks for 2 seconds!
// โ
Good: Break into chunks
async function processItems(items) {
for (let i = 0; i < items.length; i++) {
heavyOperation(items[i]);
// Yield to browser every 50ms
if (i % 10 === 0) {
await new Promise(resolve => setTimeout(resolve, 0));
}
}
}
Font Optimizationsโ
1. Preload + font-displayโ
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload"
href="/fonts/Inter-Regular.woff2"
as="font"
type="font/woff2"
crossorigin>
<style>
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter-Regular.woff2') format('woff2');
font-display: swap; /* Show fallback immediately */
}
</style>
2. Font Subsettingโ
# Full font
Inter-Regular.woff2: 150 KB
# Subset (Latin only)
Inter-Regular-Latin.woff2: 30 KB (-80%)
Tools:
- glyphhanger
- subfont
- Font Squirrel
Image Optimizationsโ
1. Prioritize LCP Imageโ
<link rel="preload" href="/hero.webp" as="image">
<img src="/hero.webp"
fetchpriority="high"
loading="eager"
width="1920"
height="1080">
2. Lazy Load Below-Foldโ
<img src="/gallery-1.jpg" loading="lazy" width="400" height="300">
<img src="/gallery-2.jpg" loading="lazy" width="400" height="300">
3. Use Modern Formatsโ
<picture>
<source srcset="/hero.avif" type="image/avif">
<source srcset="/hero.webp" type="image/webp">
<img src="/hero.jpg" alt="Hero">
</picture>
File size comparison:
JPEG: 100 KB
WebP: 60 KB (-40%)
AVIF: 40 KB (-60%)
Visual Flow Diagramsโ
Synchronous Script Blockingโ
Async Script (Non-Blocking)โ
Defer Script (Best Practice)โ
Resource Priority Timelineโ
Real Interview Scenariosโ
โ Why is CSS render-blocking but not parse-blocking?โ
Answer:
CSS is render-blocking because the browser needs CSSOM to calculate layout and paint pixels. However, it doesn't block HTML parsing because:
- Browser can parse HTML independently - Building DOM doesn't require style information
- Progressive rendering optimization - Browser continues discovering resources while CSS downloads
- Parallel operations - DOM construction and CSSOM construction happen in parallel
Example:
<html>
<head>
<link rel="stylesheet" href="styles.css"> <!-- Downloads while parsing continues -->
</head>
<body>
<div>Content</div> <!-- Parsed immediately -->
<img src="photo.jpg"> <!-- Discovered and queued -->
</body>
</html>
Timeline:
0ms: Start parsing HTML
50ms: Discover CSS, start download (parsing continues)
100ms: Parse <div>, <img> tags
200ms: HTML parsing complete (DOM ready)
300ms: CSS download complete
350ms: CSSOM built
350ms: First render โ CSS was blocking this
โ Why does async JavaScript still sometimes hurt performance?โ
Answer:
While async doesn't block during download, execution can still hurt performance because:
- Execution blocks main thread - When script executes, it can delay rendering
- Unpredictable timing - Might execute during critical rendering phase
- Parser pause - Execution interrupts HTML parsing if still in progress
- No guarantee on readiness - Might execute before DOM elements it needs
Example of bad timing:
<html>
<body>
<div id="hero">...</div>
<script src="heavy.js" async></script> <!-- Executes whenever ready -->
<!-- More content... -->
</body>
</html>
If heavy.js finishes downloading while browser is trying to render, it blocks rendering.
Better approach:
<script src="heavy.js" defer></script> <!-- Waits until HTML complete -->
Or:
<script src="analytics.js" async></script> <!-- OK for analytics -->
โ What is the safest script loading strategy for modern web apps?โ
Answer:
defer + code splitting is the safest default strategy:
<!-- Critical scripts with defer -->
<script src="/main.js" defer></script>
<script src="/components.js" defer></script>
<!-- Analytics with async (independent) -->
<script src="https://analytics.com/script.js" async></script>
Why defer is safest:
- โ Non-blocking - Downloads in parallel with HTML parsing
- โ Predictable order - Executes in document order
- โ DOM ready - Full DOM available when script runs
- โ After CSSOM - Styles are available for queries
- โ Before DOMContentLoaded - Runs before event fires
When to use async:
Only for truly independent scripts:
- Analytics (Google Analytics, Mixpanel)
- Ad networks
- Social widgets (Facebook, Twitter)
- Error tracking (Sentry)
Modern pattern:
<head>
<!-- Framework -->
<script src="/react.js" defer></script>
<script src="/react-dom.js" defer></script>
<!-- App code -->
<script src="/app.js" defer></script>
<!-- Independent scripts -->
<script src="https://www.google-analytics.com/analytics.js" async></script>
</head>
With code splitting:
// main.js
const routes = {
home: () => import('./pages/Home'),
about: () => import('./pages/About'),
contact: () => import('./pages/Contact')
};
// Load route dynamically
const page = await routes[currentRoute]();
โ How do you debug render-blocking resources?โ
Answer:
Use these tools and techniques:
1. Chrome DevTools Network Panel:
Filter by:
- Priority column: Look for "Highest" and "High"
- Waterfall: Find resources that delay first paint
- Size: (from cache) vs (from disk cache)
2. Coverage Tool:
Chrome DevTools โ More tools โ Coverage
Shows unused CSS/JS that's blocking unnecessarily
3. Lighthouse Audit:
"Eliminate render-blocking resources"
Lists specific files to optimize
4. WebPageTest:
Shows visual timeline
Highlights blocking resources clearly
Example findings:
โ Problem: styles.css (150 KB, blocks 800ms)
โ
Solution: Inline critical CSS, defer rest
โ Problem: jquery.js (90 KB, blocks 300ms)
โ
Solution: Remove jQuery, use native JS
โ Problem: fonts block text for 2s
โ
Solution: font-display: swap + preload
โ Can images ever be render-blocking?โ
Answer:
Images themselves are NOT render-blocking, but they can affect LCP (Largest Contentful Paint) indirectly:
Scenario 1: LCP Element is an Image
<!-- Hero image discovered late -->
<img src="/hero.jpg" alt="Hero">
Problem: Browser doesn't know it's important until it parses the <img> tag.
Solution:
<!-- Hint browser to load early -->
<link rel="preload" href="/hero.jpg" as="image">
<img src="/hero.jpg" fetchpriority="high" loading="eager">
Scenario 2: Image Loaded via CSS
.hero {
background-image: url('/hero.jpg'); /* Discovered late! */
}
Problem: Browser must download CSS, parse it, then discover image.
Solution:
<!-- Preload background image -->
<link rel="preload" href="/hero.jpg" as="image">
Scenario 3: Missing Dimensions
<!-- Causes layout shift (CLS) -->
<img src="/photo.jpg" alt="Photo">
<!-- Prevents reflow -->
<img src="/photo.jpg" width="800" height="600" alt="Photo">
While not technically "blocking", layout shifts hurt user experience.
Quick Referenceโ
Resource Blocking Quick Matrixโ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโ
โ Resource โ Blocks Parse? โ Blocks Render? โ Solution โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโค
โ CSS โ โ โ โ
โ Inline crit. โ
โ JS (sync) โ โ
โ โ
โ Use defer โ
โ JS (async) โ โ โ โ โ For analyticsโ
โ JS (defer) โ โ โ โ โ Default โ
โ
โ Images โ โ โ โ โ Optimize LCP โ
โ Fonts โ โ โ โ ๏ธ โ font-display โ
โโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโ
Script Loading Decision Treeโ
Need to load JavaScript?
โโ Does it manipulate DOM?
โ โโ Yes โ Use defer
โ โโ No โ Is it independent (analytics)?
โ โโ Yes โ Use async
โ โโ No โ Use defer
โ
โโ Can it load later (below-fold)?
โโ Yes โ Dynamic import / lazy load
โโ No โ Use defer
CSS Loading Decision Treeโ
Need to load CSS?
โโ Above-the-fold styles?
โ โโ Yes โ Inline in <style> tag
โ โโ No โ External stylesheet
โ
โโ Large stylesheet?
โ โโ Yes โ Split into critical + non-critical
โ โโ No โ Single external file
โ
โโ Media-specific?
โโ Yes โ Use media queries
โโ No โ Load normally
Optimization Priority Orderโ
High Impact, Low Effort:
- โ
Add
deferto all app scripts - โ Inline critical CSS (< 14 KB)
- โ Preload LCP image
- โ
Add
font-display: swap
Medium Impact, Medium Effort: 5. โ Code split JavaScript 6. โ Minify CSS/JS 7. โ Enable compression (Gzip/Brotli) 8. โ Lazy load below-fold images
High Impact, High Effort: 9. โ Remove unused CSS/JS 10. โ Implement service worker 11. โ Switch to HTTP/2 12. โ Optimize third-party scripts
Blocking Time Estimatesโ
Typical blocking times by resource type:
| Resource | Avg Blocking Time | Impact Level |
|---|---|---|
| External CSS (50 KB) | 200-400ms | ๐ด High |
| Sync JS (100 KB) | 300-600ms | ๐ด High |
| Web font (30 KB) | 100-300ms | ๐ก Medium |
| Render-blocking above | 500-1000ms combined | ๐ด Critical |
Target: Keep total blocking time under 600ms.
Common Blocking Patterns to Avoidโ
โ Pattern 1: Multiple Blocking Scriptsโ
<!-- BAD: 3 blocking scripts = 900ms+ -->
<script src="jquery.js"></script>
<script src="bootstrap.js"></script>
<script src="app.js"></script>
Fix:
<!-- GOOD: All deferred = 0ms blocking -->
<script src="jquery.js" defer></script>
<script src="bootstrap.js" defer></script>
<script src="app.js" defer></script>
โ Pattern 2: CSS Before Scriptsโ
<!-- BAD: Script waits for CSS -->
<link rel="stylesheet" href="styles.css">
<script src="app.js"></script>
Timeline:
0-300ms: CSS downloads (script waits)
300-400ms: CSS parses
400-700ms: Script downloads
700-800ms: Script executes
Total: 800ms blocked
Fix:
<!-- GOOD: Script loads independently -->
<link rel="stylesheet" href="styles.css">
<script src="app.js" defer></script>
โ Pattern 3: Multiple CSS Filesโ
<!-- BAD: Multiple roundtrips -->
<link rel="stylesheet" href="reset.css">
<link rel="stylesheet" href="layout.css">
<link rel="stylesheet" href="components.css">
<link rel="stylesheet" href="theme.css">
Fix:
<!-- GOOD: Single bundled file -->
<link rel="stylesheet" href="styles.min.css">
<!-- OR: Critical inline + rest async -->
<style>/* Critical CSS */</style>
<link rel="preload" href="styles.css" as="style"
onload="this.rel='stylesheet'">
โ Pattern 4: Late Font Discoveryโ
<link rel="stylesheet" href="styles.css">
<!-- Font discovered only after CSS downloads -->
Fix:
<!-- GOOD: Preload font -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload"
href="/fonts/Inter-Regular.woff2"
as="font"
type="font/woff2"
crossorigin>
<link rel="stylesheet" href="styles.css">
Performance Checklistโ
Before deploying:
Critical Resourcesโ
- No more than 2-3 render-blocking CSS files
- All JavaScript uses
deferorasync - Critical CSS inlined (< 14 KB)
- LCP image preloaded
JavaScriptโ
- No synchronous scripts in
<head> - Third-party scripts use
async - Bundle size < 200 KB (gzipped)
- Code splitting implemented
CSSโ
- Single bundled CSS file (or critical inline)
- Unused CSS removed
- Minified and compressed
- Media queries used for print/mobile-specific styles
Fontsโ
- Critical fonts preloaded
-
font-display: swapset - Fonts subset (if possible)
- Preconnect to font CDN
Imagesโ
- LCP image has
fetchpriority="high" - Below-fold images lazy loaded
- Dimensions specified (width/height)
- Modern formats used (WebP/AVIF)
Testingโ
- Lighthouse score > 90
- LCP < 2.5s
- FCP < 1.8s
- No blocking warnings in DevTools
Real-World Performance Gainsโ
Case Study 1: E-commerce Product Pageโ
Before:
Render-blocking resources:
- styles.css (150 KB) - 400ms
- jquery.js (90 KB) - 300ms
- app.js (200 KB) - 500ms
Total blocking: 1,200ms
LCP: 3.8s
After:
<style>/* Critical CSS - 8 KB */</style>
<link rel="preload" href="styles.css" as="style" onload="this.rel='stylesheet'">
<script src="app.js" defer></script>
<!-- Removed jQuery, used native JS -->
Results:
Total blocking: 0ms (critical CSS inline)
LCP: 1.4s (63% improvement)
Lighthouse: 67 โ 94
Case Study 2: News Websiteโ
Before:
Render-blocking:
- fonts (4 weights) - 600ms
- analytics.js - 200ms
- ads.js - 300ms
Total: 1,100ms
FCP: 2.9s
After:
<link rel="preload" href="/font-regular.woff2" as="font" crossorigin>
<style>
@font-face {
font-family: 'Inter';
font-display: swap;
}
</style>
<script src="analytics.js" async fetchpriority="low"></script>
<script src="ads.js" async fetchpriority="low"></script>
Results:
Total blocking: 150ms (1 font)
FCP: 1.1s (62% improvement)
User engagement: +23%
Case Study 3: SaaS Dashboardโ
Before:
Blocking:
- bootstrap.css (200 KB) - 500ms
- bootstrap.js (80 KB) - 250ms
- app bundle (500 KB) - 800ms
Total: 1,550ms
TTI: 4.5s
After:
<!-- Critical CSS only -->
<style>/* 12 KB critical */</style>
<!-- Code splitting -->
<script src="main.js" defer></script>
<!-- Routes load dynamically -->
<!-- Removed Bootstrap -->
<!-- Used Tailwind (9 KB) -->
Results:
Total blocking: 0ms
Main bundle: 50 KB (90% reduction)
TTI: 1.2s (73% improvement)
Browser Behavior Differencesโ
Different browsers handle blocking slightly differently:
Chrome/Edgeโ
- Aggressive preload scanner
- Parallel CSSOM + DOM construction
- HTTP/2 prioritization
Firefoxโ
- More conservative preloading
- Slightly different priority heuristics
- Good async handling
Safariโ
- Most conservative preloading
- Stricter CORS handling for fonts
- Benefits most from explicit hints
Recommendation: Always test in multiple browsers, but Chrome DevTools gives good baseline metrics.
Advanced Optimization: Critical Request Chainsโ
Critical Request Chain: The sequence of dependent network requests that block rendering.
Example chain:
1. HTML (0ms)
โโ 2. CSS (200ms)
โ โโ 3. Font (400ms)
โโ 4. JS (300ms)
โโ 5. API call (500ms)
Chain depth: 5 levels Total time: 1,400ms
Optimized chain:
1. HTML (0ms) with inlined critical CSS
โโ 2. Font (preloaded, 200ms parallel)
โโ 3. Full CSS (deferred)
โโ 4. JS (defer, 200ms parallel)
โโ 5. API (prefetch, parallel)
Chain depth: 2 levels Total time: 200ms
Tools to analyze:
- Chrome DevTools โ Performance โ Bottom-Up
- Lighthouse โ Diagnostics โ Critical Request Chains
- WebPageTest โ Waterfall view
One-Line Interview Summaryโ
"Blocking resources delay parsing or rendering by forcing the browser to wait; minimize them by inlining critical CSS, deferring JavaScript, and using resource hints to parallelize loading on the critical path."
Key Takeawaysโ
- CSS blocks rendering but not parsing - necessary for layout
- Synchronous JS blocks both - most harmful for performance
deferis the safe default - non-blocking, predictable, DOM-readyasyncfor independent scripts - analytics, ads, widgets- Images never block - but optimize LCP image priority
- Fonts delay text - use
font-display: swap+ preload - Measure before optimizing - use Lighthouse and DevTools
- Inline critical CSS - biggest single performance win
Further Readingโ
Official Documentation:
Tools:
- Chrome DevTools (Network, Performance, Lighthouse)
- WebPageTest
- PageSpeed Insights
- Critical CSS generators
Related Topics:
- ๐ฅ Browser Caching Strategies
- ๐ฅ Browser Hinting Techniques
- ๐ฅ Critical Rendering Path
- ๐ฅ Web Vitals Optimization
Last Updated: December 2025